//////////////////////////////////////////////////////////////////////////////
//
//  Created by Philip Mulcahy on 29/05/2012.
//  Copyright (c) 2012 Philip Mulcahy. All rights reserved.
//
//  This file is part of the note-recog library.
//
//  note-recog is free software: you can redistribute it and/or modify
//  it under the terms of version 3 of the GNU Lesser General Public License 
//  as published by the Free Software Foundation.
//
//  note-recog is distributed is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with note-recog.  If not, see <http://www.gnu.org/licenses/>.
//
//////////////////////////////////////////////////////////////////////////////


#include <iostream>
#include <string>
#include <vector>
#include "Rememberer.h"
#include "TestData.h"
#include "Condenser.h"
#include "FastFourierTransform.h"
#include "Spectrum.h"
#include "Slice.h"
#include "Harmonic.h"
#include "INoteEvent.h"
#include "Log.h"

LOG_DEFINE(Rememberer);

Rememberer::Rememberer()
{}


Rememberer::Rememberer( const Notes& expecteds ) 
  : _expecteds(expecteds) 
{}
 

Rememberer::Notes Rememberer::playDataIntoCondenser( const char * name,
                                                     const Condenser::InitInfo& info ) {
    Rememberer rem;
    rem.playDataViaCondenser(name,info);
    return rem.getNotes();
}


void Rememberer::playDataViaCondenser( const char * name, 
                                       const Condenser::InitInfo& info ) {
    const std::vector<short>& input = TestData::get(name);
    Condenser condenser(this,info);
    const size_t n = 2048;
    std::vector<short> sample(n);
    const float sampleRate = 22050.0;
    const float sampleTimeInterval = n / sampleRate;
    float time = 0.f;
    size_t frameNumber = 0;
    FastFourierTransform fft(sample,sampleRate);
    sample.clear();
    for( std::vector<short>::const_iterator itShort = input.begin();
        itShort != input.end(); 
        ++itShort 
        )
    {
        const short frame = *itShort;
        sample.push_back(frame);
        if( sample.size() == n )
        {
            const Spectrum spec = fft.calculate();
            
            Slice slice(
                        frameNumber,
                        time,
                        spec,
                        input
                        );
            
            LOG_DEBUG( "Slice dominant: " << slice.dominant() << std::endl );
            
            condenser.submit(slice);
            
            frameNumber += n;
            time += sampleTimeInterval / 2;
            sample.resize(sample.size()/2);
        }
    }
}


void Rememberer::onNoteRecognized(const INoteEvent& evt) {
    _actuals[evt.getStartTime()] = evt.getFrequency();
}


const Rememberer::Notes& Rememberer::getNotes() const {
    return _actuals;
}


// If it finds a reasonable match for the input note
// it removes the matched entry from expected and returns 
// true, otherwise returns false and leaves expected alone.
static bool found(
                  const float& actualTimestamp,
                  const float& actualFreq,
                  Rememberer::Notes& expecteds ) {
    const float timeTolS = 0.2f;
    const float freqRatLogTol = 0.5 * log(2.0f) / 12.0f;
    
    for( auto& expected : expecteds ) {
        const float expectedTimestamp = expected.first;
        const float expectedFreq = expected.second;
        const float timeDiff = abs(expectedTimestamp - actualTimestamp);
        const float freqRat = actualFreq / expectedFreq;
        const float freqRatLog = abs(log(freqRat));
        const bool timeGood = timeDiff <= timeTolS;
        const bool freqGood = freqRatLog <= freqRatLogTol;
        const bool good = timeGood && freqGood;
        if( good ) {
            expecteds.erase(expected.first);
            return true;
        }
    }
    return false;
}

float Rememberer::validate() const {
    float errorCount = 0.f;
        
    Notes _remainingExpecteds = _expecteds;
    
    for( auto& actual : _actuals ) {
        const float actualTimestamp = actual.first;
        const float actualFrequency = actual.second;
        const bool noteFound = found(
                                     actualTimestamp,
                                     actualFrequency,
                                     _remainingExpecteds );
        if( noteFound ) {
            char msg[250];
            sprintf( 
                    msg, 
                    "MATCHED:t=%f s, f=%f hz",
                    actualTimestamp,
                    actualFrequency);
            LOG_DEBUG( msg );
        }
        else {
            const bool octaveUpFound = found(
                                             actualTimestamp,
                                             actualFrequency / 2.f,
                                             _remainingExpecteds );

            if( octaveUpFound ) {
                char msg[250];
                sprintf( 
                        msg, 
                        "OCTAVE OUT:t=%f s, f=%f hz",
                        actualTimestamp,
                        actualFrequency);
                LOG_DEBUG( msg );
                errorCount += 0.5f;
            }
            else {
                char msg[250];
                sprintf( 
                        msg, 
                        "FAIL: Actual note not expected: t=%f s, f=%f hz",
                        actualTimestamp,
                        actualFrequency);
                LOG_DEBUG( msg );
                errorCount += 1.f;
            }
        }
    }
    
    for( Notes::value_type& unmatched : _remainingExpecteds ) {
        const float timestamp = unmatched.first;
        const float frequency = unmatched.second;
        char msg[250];
        sprintf( 
                msg,
                "FAIL: Expected note not found: t=%f s, f=%f hz",
                timestamp,
                frequency);
        LOG_DEBUG( msg );
        errorCount += 1.f;
    }
    
    return errorCount;
}
